"""
python p2_solution_deap_genetic.py
"""
"""
这个 Python 脚本使用 DEAP 库实现遗传算法来解决电商商品品类分仓规划问题。

目标是在满足一定约束条件下，为每个商品品类分配一个仓库，使得总成本最小且使用的仓库数量最少。

约束条件包括：仓库的存货量不能超过其库存上限，仓库的出货量不能超过其出货量上限。

最终输出分仓方案的 JSON 文件，以及总成本、库存量利用率和出货量利用率的文件。
"""

import json
import os
from deap import base, creator, tools
import random

# 设置随机数种子以保证结果可复现
random.seed(114514)

# 读取仓库信息
def read_warehouse_data():
    with open('../fujian/fujian3/origin_data/warehouse.json') as f:
        return json.load(f)

# 读取品类平均库存量
def read_category_inventory():
    with open('../fujian/fujian3/data_from_p1/all_average_inventory.json') as f:
        return {d['category_id']: d['average_inventory'] for d in json.load(f)}

# 读取品类平均销量
def read_category_sales():
    with open('../fujian/fujian3/data_from_p1/all_average_sales.json') as f:
        return {d['category_id']: d['average_sales'] for d in json.load(f)}

# 读取预设解决方案
def read_preset_solution():
    with open('../fujian/p2/simple_solution_handwrite/ans.json') as f:
        data = json.load(f)
        return [int(item[list(item.keys())[0]]) for item in data]

# 数据读取
warehouses = read_warehouse_data()
category_inventories = read_category_inventory()
category_sales = read_category_sales()
preset_solution = read_preset_solution()

# 创建适应度函数和个体类型
creator.create("FitnessMin", base.Fitness, weights=(-1.0, -1.0))  # 最小化成本和使用的仓库数量
creator.create("Individual", list, fitness=creator.FitnessMin)

toolbox = base.Toolbox()
toolbox.register("individual", lambda: creator.Individual(preset_solution.copy()))
toolbox.register("population", tools.initRepeat, list, toolbox.individual)

def evaluate(individual):
    total_cost = 0
    used_warehouses = set()
    warehouse_inventories = [0] * len(warehouses)
    warehouse_sales = [0] * len(warehouses)
    
    for category_index, warehouse_index in enumerate(individual):
        warehouse_inventories[warehouse_index] += category_inventories[str(category_index + 1)]
        warehouse_sales[warehouse_index] += category_sales[str(category_index + 1)]
        used_warehouses.add(warehouse_index)
    
    # 检查约束条件
    for warehouse_index, warehouse in enumerate(warehouses):
        if (warehouse_inventories[warehouse_index] >= warehouse['max_inventory'] or
                warehouse_sales[warehouse_index] >= warehouse['max_sales']):
            return float('inf'), float('inf')  # 不合法解
        if warehouse_inventories[warehouse_index] > 0:
            total_cost += warehouse['daily_cost']
    
    return total_cost, len(used_warehouses)

toolbox.register("evaluate", evaluate)
toolbox.register("mate", tools.cxTwoPoint)
toolbox.register("mutate", tools.mutUniformInt, low=0, up=len(warehouses) - 1, indpb=0.2)
toolbox.register("select", tools.selTournament, tournsize=3)

# 初始化种群
population_size = 100
pop = toolbox.population(n=population_size)

# 全局最优个体及其成本
absolute_best_individual = None
absolute_best_cost = float('inf')

gen = 0  # 代数
output_folder_base = '../fujian/fujian3/deap_genetic/gen'  # 输出文件夹基础路径
best_ever_folder = '../fujian/fujian3/deap_genetic/best_ever'  # 最佳个体输出文件夹
os.makedirs(best_ever_folder, exist_ok=True)

try:
    while True:
        gen += 1
        offspring = toolbox.select(pop, len(pop))
        offspring = list(map(toolbox.clone, offspring))
        
        # 交叉操作
        for child1, child2 in zip(offspring[::2], offspring[1::2]):
            if random.random() < 0.7:
                toolbox.mate(child1, child2)
                del child1.fitness.values
                del child2.fitness.values
        
        # 突变操作
        for mutant in offspring:
            if random.random() < 0.2:
                toolbox.mutate(mutant)
                del mutant.fitness.values
        
        # 计算适应度
        invalid_ind = [ind for ind in offspring if not ind.fitness.valid]
        fitnesses = map(toolbox.evaluate, invalid_ind)
        for ind, fit in zip(invalid_ind, fitnesses):
            ind.fitness.values = fit
        
        pop[:] = offspring

        # 选择当前代的最优个体
        best_individual = tools.selBest(pop, 1)[0]

        # 计算当前最优个体的总成本
        total_cost = 0
        warehouse_inventories = [0] * len(warehouses)
        warehouse_sales = [0] * len(warehouses)

        for category_index, warehouse_index in enumerate(best_individual):
            warehouse_inventories[warehouse_index] += category_inventories[str(category_index + 1)]
            warehouse_sales[warehouse_index] += category_sales[str(category_index + 1)]

        for warehouse_index, warehouse in enumerate(warehouses):
            if warehouse_inventories[warehouse_index] > 0:
                total_cost += warehouse['daily_cost']

        # 更新全局最优解
        if total_cost < absolute_best_cost:
            absolute_best_cost = total_cost
            absolute_best_individual = best_individual[:]
            print(f"New Absolute Best Cost: {absolute_best_cost} at Generation {gen}")

            # 立即输出全局最优个体到文件
            absolute_best_result = [{"c_id": str(category_index + 1), "w_id": warehouses[warehouse_index]['warehouse_id']}
                                    for category_index, warehouse_index in enumerate(absolute_best_individual)]
            
            with open(os.path.join(best_ever_folder, 'absolute_best_individual_gene.json'), 'w') as f:
                json.dump(absolute_best_result, f, indent=4)

            # 输出当前全局最优个体的成本到文件
            with open(os.path.join(best_ever_folder, 'best_cost.txt'), 'a') as f:
                f.write(f"{absolute_best_cost}\n")

        # 输出当前最优个体的总成本
        print(f"Generation {gen}, Best Cost: {total_cost}")

except KeyboardInterrupt:
    print("Optimization interrupted by user.")

